#include "Partida.h"

#include "VistaFichaTablero.h"
#include "VistaButton.h"
#include "VistaMiniBar.h"
#include "VistaGoldenStar.h"
#include "VistaHueco.h"
#include "DesplazadorFichas.h"
#include "DesplazadorColumna.h"
#include "DesaparecedorFicha.h"
#include "Mensaje.h"
#include "Configuracion.h"
#include "Protocolo.h"

#include "AparecedorFicha.h"
#include "DesaparecedorFicha.h"
#include "DesplazadorFichas.h"
#include "DesplazadorColumna.h"

#include "ParserTableros.h"
#include <list>
#include <string>
#include <map>
#include "Log.h"


Partida::Partida(int idPartida, Json::Value vecJug, Json::Value datosNivel,
                 Json::Value vecFichas, PanelJuego *panelJuego, int nroFilas,
                 int nroCols, std::string pathImagenFondo, 
                 Gtk::Statusbar* status) {
    this->nroFilas = nroFilas;
    this->nroCols = nroCols;

    m_protectoFichas = new Mutex();

    this->idPartida = idPartida;

    this->animacion_invalida = NULL;
    this->animacion_valida = NULL;

    this->panelJuego = panelJuego;
    Partida::statusBar=status;

    cargarJugadores(vecJug);

    cargarDatosNivel(datosNivel);

    cargarFichas(vecFichas);

    panelJuego->agregarPanelScoreJug(jugadores);

    x1 = y1 = x2 = y2 = NO_SELECCIONADO;
}
/* Para protejer el tablero */
Mutex Partida::m_animando;
bool Partida::animando=false;
Mutex Partida::m_status;
Gtk::Statusbar* Partida::statusBar;

Partida::~Partida() {
    if (this->animacion_valida!=NULL){
        // Espera finalizacion de animacion.
        this->animacion_valida->join();
        delete this->animacion_valida;
        this->animacion_valida=NULL;
    }

    if (this->animacion_invalida!=NULL){
        delete this->animacion_invalida;
        this->animacion_invalida=NULL;
    }

    Log::loguear(MINFO,"Eliminando partida...");
    delete m_protectoFichas;

    std::map<std::string, Usuario *>:: iterator it;
    Log::loguear(MINFO,"> Eliminando jugadores...");
    for (it = jugadores.begin(); it != jugadores.end(); it++) {
        if (it->second != NULL) {
            delete it->second;
        }
    }

    this->eliminarContenido();
}

bool Partida::estaAnimando(){
return Partida::animando;
}

void Partida::setAnimando(bool estado){
    m_animando.lock();
    Partida::animando=estado;
    m_animando.unlock();
}

void Partida::mostrarMensaje(std::string mensaje){
    m_status.lock();
    Partida::statusBar->pop();
    Partida::statusBar->push(mensaje);
    m_status.unlock();
}

void Partida::notificarRetiro() {
    Mensaje mensaje_envio;

    mensaje_envio.data["req_type"] = REQ_COMANDO_PARTIDO;
    mensaje_envio.data["tipo"] = TIPO_COM_DESCONECTAR_JUGADOR;
    mensaje_envio.setKey(clave);

    std::string serial = mensaje_envio.serializar();

    cliente->enviarConAvisoDeLong((char *)serial.c_str(),
                                  (uint32_t)serial.length());
}

void Partida::eliminarContenido() {
    this->panelJuego->vaciarPartidas();
}

void Partida::cargarFichas(Json::Value tablero) {
    VistaFichaTablero *vistaFicha = NULL;
    std::string candy_str, color_str;
    Json::Value fichaJson;
    int x,y;
    for (unsigned int i = 0; i < tablero.size(); i++) {
        fichaJson.clear();
        fichaJson= tablero[i];

        vistaFicha= NULL;
        candy_str= fichaJson["candy"].asString();
        color_str= fichaJson["color"].asString();
        x = fichaJson["x"].asInt();
        y = fichaJson["y"].asInt();

        if (candy_str == BUTTON) {
            vistaFicha = new VistaButton(color_str,true, x, y);
        } else if (candy_str == MINIBARH) {
            vistaFicha = new VistaMiniBar(color_str,true, x, y, HORIZONTAL);
        } else if (candy_str == MINIBARV) {
            vistaFicha = new VistaMiniBar(color_str,true, x, y, VERTICAL);
        } else if (candy_str == GOLDENSTAR) {
            vistaFicha = new VistaGoldenStar(color_str,true, x, y);
        } else if (candy_str == HUECO) {
            vistaFicha = new VistaHueco(color_str,true, x, y);
        }

        m_protectoFichas->lock();
        panelJuego->dibujador->vistaFichas[x][y] = vistaFicha;
        m_protectoFichas->unlock();
    }
}

void Partida::cargarDatosNivel(Json::Value datosNivel) {
    panelJuego->establecerDatosNivel(datosNivel["nombre"].asString(),
                                 std::to_string(datosNivel["numero"].asInt()),
                                 std::to_string(datosNivel["puntos"].asInt()),
                                 datosNivel["numFilas"].asInt(),
                                 datosNivel["numCols"].asInt());
    std::stringstream ss;
    std::string ruta;
    /* Si no puede abrir el audio enviado se usará el predeterminado */
    if (!datosNivel["sonidoCombinacion"].isNull() && 
        datosNivel["sonidoCombinacion"].asString()!=""){
      ss << RUTA_SONIDOS_CLIENTE << "/" 
         << datosNivel["sonidoCombinacion"].asString();
      ruta= ss.str();
      Juego::reproductorWav.setAudio(SON_COMBINACION,ruta);
      ss.str("");
      ss << "Se modifica sonido predeterminado de combinacion (editor): ";
      ss << ruta;
      Log::loguear(MINFO, ss.str());
    }
    if (!datosNivel["sonidoMovimiento"].isNull() && 
        datosNivel["sonidoMovimiento"].asString()!=""){
      ss.str("");
      ss << RUTA_SONIDOS_CLIENTE << "/" 
         << datosNivel["sonidoMovimiento"].asString();
      ruta= ss.str();
      Juego::reproductorWav.setAudio(SON_MOVIMIENTO,ruta);
      ss.str("");
      ss << "Se modifica sonido predeterminado de movimiento (editor): ";
      ss << ruta;
      Log::loguear(MINFO, ss.str());
    }

    if (!datosNivel["imagenFondo"].isNull() && 
      datosNivel["imagenFondo"].asString()!=""){
      ss.str("");
      ss << RUTA_FONDOS_CLIENTE << "/" 
         << datosNivel["imagenFondo"].asString();
      ruta= ss.str();
      panelJuego->setImagenFondoTablero(ruta);
      ss.str("");
      ss << "Se modifica imagen de fondo predeterminada por (editor): ";
      ss << ruta;
      Log::loguear(MINFO, ss.str());
    } else {
      panelJuego->setImagenFondoTablero(IMAGEN_PREDETERMINADA_GAME);
    }
}

void Partida::cargarJugadores(Json::Value vecJug) {
    Json::Value jugJson;

    for (unsigned int i = 0; i < vecJug.size(); i++) {
        jugJson = vecJug[i];
        jugadores[jugJson["nombre"].asString()] = new Usuario(
            jugJson["nombre"].asString(), jugJson["puntos"].asInt());
    }
}

void Partida::establecerClave(std::string clave) {
    this->clave = clave;
}

void Partida::actualizarPuntajes(Json::Value puntajes) {
    Json::Value datosUnUsuario;

    for (size_t i = 0; i < puntajes.size(); i++) {
        datosUnUsuario = puntajes[i];
        jugadores[datosUnUsuario["nombre"].asString()]->puntaje =
            datosUnUsuario["puntos"].asInt();
    }

    panelJuego->actualizarPuntaje(jugadores);
}

void Partida::actualizarTablero(Json::Value movimiento, Json::Value& tableros) {
    ParserTableros parserTableros(tableros, nroFilas, nroCols);

    std::list<Json::Value> *animaciones = parserTableros.obtenerAnimaciones();
    // Inserto el movimiento que desencadeno la animacion.
    animaciones->push_front(movimiento);

    // Libera recursos usados por animaciones anteriores.
    if (this->animacion_valida!=NULL){
        Log::loguear(MINFO,"Eliminando animacion previa (0/4)...");
        this->animacion_valida->join();
        // Se liberan los recursos encadenados.
        delete this->animacion_valida;
    }

    this->animacion_valida = new DesplazadorFichas(animaciones,
            panelJuego->dibujador, m_protectoFichas, true);

    this->animacion_valida->start();
}

void Partida::actualizarPartida(Json::Value movimiento, Json::Value puntajes,
                                Json::Value* tableros) {
    actualizarTablero(movimiento, *tableros);

    actualizarPuntajes(puntajes);
}

void Partida::solicitarMovimiento() {
    // Valido que haga click adentro del tablero
    if (x1 >= nroFilas || x2 >= nroFilas || y1 >= nroCols || y2 >= nroCols) {
        return;
    }

    // Alguna de las coordenadas tiene que ser constante
    if (x1 != x2 && y1 != y2) {
        return;
    }

    // Valido que la coordenada que se mueve, lo haga en 1 unidad
    if (x1 == x2 && y1 != y2 - 1 && y1 != y2 + 1) {
        return;
    }

    if (y1 == y2 && x1 != x2 - 1 && x1 != x2 + 1) {
        return;
    }

    mov["x1"] = x1;
    mov["y1"] = y1;
    mov["x2"] = x2;
    mov["y2"] = y2;

    Mensaje mensaje_envio;

    mensaje_envio.data["req_type"] = REQ_COMANDO_PARTIDO;
    mensaje_envio.data["tipo"] = 0;
    mensaje_envio.data["argumentos"] = mov;

    mensaje_envio.setKey(clave);

    std::string serial = mensaje_envio.serializar();

    cliente->enviarConAvisoDeLong((char *)serial.c_str(),
                                  (uint32_t)serial.length());
}

void Partida::establecerComunicador(Socket *cliente) {
    this->cliente = cliente;
}

void Partida::vaciar() {
    x1 = y1 = x2 = y2 = NO_SELECCIONADO;
}

void Partida::animarMovInvalido(Json::Value movInvalido) {
    std::list<Json::Value> *animaciones = new std::list<Json::Value>;
    animaciones->push_back(movInvalido);

    if (this->animacion_invalida!=NULL){
        Log::loguear(MDEBUG, "Eliminando animación inválida previa...");
        this->animacion_invalida->join();
        delete this->animacion_invalida;
        this->animacion_invalida=NULL;
    }

    this->animacion_invalida = new DesplazadorFichas(animaciones,
            panelJuego->dibujador, m_protectoFichas, false);
    this->animacion_invalida->start();
}

void Partida::seleccionarFicha(int x, int y) {
   if (!Partida::estaAnimando()){
        std::stringstream ss;
        if (x1 == NO_SELECCIONADO && y1 == NO_SELECCIONADO) {
            ss << "Origen: (" << x << "," << y << ")";
            x1 = x;
            y1 = y;
        } else {
            if (x==x1 && y==y1){
                ss << "Seleccionaste la misma!";
                vaciar(); // reinicio movimiento
            } else {
                ss << "Destino: (" << x << "," << y << ")";
                x2 = x;
                y2 = y;
                Partida::mostrarMensaje("Procesando, aguarde por favor...");
                solicitarMovimiento();
                vaciar();
            }
        }
        Log::loguear(MDEBUG,ss.str());
   } else {
    Partida::mostrarMensaje("Aguarde finalizacion de animacion por favor...");
   }
}
